package dto

import (
	"fmt"
	"reflect"
	"strings"

	"gitee.com/sansaniot/ssiot-core/config"
	"gitee.com/sansaniot/ssiot-core/httpmvc/search"
	"gorm.io/gorm"
)

type GeneralDelDto struct {
	Id  int   `uri:"id" json:"id" validate:"required"`
	Ids []int `json:"ids"`
}

func (g GeneralDelDto) GetIds() []int {
	ids := make([]int, 0)
	if g.Id != 0 {
		ids = append(ids, g.Id)
	}
	if len(g.Ids) > 0 {
		for _, id := range g.Ids {
			if id > 0 {
				ids = append(ids, id)
			}
		}
	} else {
		if g.Id > 0 {
			ids = append(ids, g.Id)
		}
	}
	if len(ids) <= 0 {
		//方式全部删除
		ids = append(ids, 0)
	}
	return ids
}

type GeneralGetDto struct {
	Id int `uri:"id" json:"id" validate:"required"`
}

func MakeCondition(q interface{}) func(db *gorm.DB) *gorm.DB {
	return func(db *gorm.DB) *gorm.DB {
		condition := &search.GormCondition{
			GormPublic: search.GormPublic{},
			Join:       make([]*search.GormJoin, 0),
		}
		// 换成自定义的
		resolveSearchQuery(config.DatabaseConfig.Driver, q, condition)
		for _, join := range condition.Join {
			if join == nil {
				continue
			}
			db = db.Joins(join.JoinOn)
			for _, v1 := range join.Where {
				for k, v := range v1 {
					db = db.Where(k, v...)
				}
			}
			for k, v := range join.Or {
				db = db.Or(k, v...)
			}
			for _, o := range join.Order {
				db = db.Order(o)
			}
		}
		for _, v1 := range condition.Where {
			for k, v := range v1 {
				db = db.Where(k, v...)
			}
		}
		for k, v := range condition.Or {
			db = db.Or(k, v...)
		}
		for _, o := range condition.Order {
			db = db.Order(o)
		}
		return db
	}
}

func Paginate(pageSize, pageIndex int) func(db *gorm.DB) *gorm.DB {
	return func(db *gorm.DB) *gorm.DB {
		offset := (pageIndex - 1) * pageSize
		if offset < 0 {
			offset = 0
		}
		return db.Offset(offset).Limit(pageSize)
	}
}

// 自定义ResolveSearchQuery 解析
/**
 * 	exact / iexact 等于
 * 	contains / icontains 包含
 *	gt / gte 大于 / 大于等于
 *	lt / lte 小于 / 小于等于
 *	startswith / istartswith 以…起始
 *	endswith / iendswith 以…结束
 *	in
 *	isnull
 *  order 排序		e.g. order[key]=desc     order[key]=asc
 */
func resolveSearchQuery(driver string, q interface{}, condition search.Condition) {
	qType := reflect.TypeOf(q)
	qValue := reflect.ValueOf(q)
	var tag string
	var ok bool
	var t *resolveSearchTag
	for i := 0; i < qType.NumField(); i++ {
		tag, ok = "", false
		tag, ok = qType.Field(i).Tag.Lookup(search.FromQueryTag)
		if !ok {
			//递归调用
			resolveSearchQuery(driver, qValue.Field(i).Interface(), condition)
			continue
		}
		switch tag {
		case "-":
			continue
		}
		t = makeTag(tag)
		if qValue.Field(i).IsZero() {
			continue
		}
		//解析
		switch t.Type {
		case "left":
			//左关联
			join := condition.SetJoinOn(t.Type, fmt.Sprintf(
				"left join \"%s\" on \"%s\".\"%s\" = \"%s\".\"%s\"",
				t.Join,
				t.Join,
				t.On[0],
				t.Table,
				t.On[1],
			))
			resolveSearchQuery(driver, qValue.Field(i).Interface(), join)
		case "exact", "iexact":
			if len(t.JsonField) > 0 {
				dt := qType.Field(i).Type.Name()
				switch dt {
				case "string":
					condition.SetWhere(fmt.Sprintf("(\"%s\".\"%s\" #>>'{%s}')::VARCHAR = ?", t.Table, t.Column, t.JsonField), []interface{}{qValue.Field(i).String()})
				default:
					condition.SetWhere(fmt.Sprintf("(\"%s\".\"%s\" #>>'{%s}')::INTEGER = ?", t.Table, t.Column, t.JsonField), []interface{}{qValue.Field(i).Interface()})
				}

			} else {
				condition.SetWhere(fmt.Sprintf("\"%s\".\"%s\" = ?", t.Table, t.Column), []interface{}{qValue.Field(i).Interface()})
			}
		case "contains", "icontains":
			//fixme mysql不支持ilike
			if driver == search.Postgres && t.Type == "icontains" {
				condition.SetWhere(fmt.Sprintf("\"%s\".\"%s\" ilike ?", t.Table, t.Column), []interface{}{"%" + qValue.Field(i).String() + "%"})
			} else if len(t.JsonField) > 0 {
				condition.SetWhere(fmt.Sprintf("\"%s\".\"%s\" #>>'{%s}' like ?", t.Table, t.Column, t.JsonField), []interface{}{"%" + qValue.Field(i).String() + "%"})
			} else {
				condition.SetWhere(fmt.Sprintf("\"%s\".\"%s\" like ?", t.Table, t.Column), []interface{}{"%" + qValue.Field(i).String() + "%"})
			}
		case "containsSplit":
			condition.SetWhere(fmt.Sprintf("\"%s\".\"%s\" like ? or \"%s\".\"%s\" like ? or \"%s\".\"%s\" = ? ", t.Table, t.Column, t.Table, t.Column, t.Table, t.Column), []interface{}{"%" + "," + qValue.Field(i).String() + "%", "%" + qValue.Field(i).String() + "," + "%", qValue.Field(i).String()})
		case "gt":
			if len(t.JsonField) > 0 {
				dt := qType.Field(i).Type.Name()
				switch dt {
				case "string":
					condition.SetWhere(fmt.Sprintf("(\"%s\".\"%s\" #>>'{%s}')::VARCHAR > ?", t.Table, t.Column, t.JsonField), []interface{}{qValue.Field(i).String()})
				default:
					condition.SetWhere(fmt.Sprintf("(\"%s\".\"%s\" #>>'{%s}')::INTEGER > ?", t.Table, t.Column, t.JsonField), []interface{}{qValue.Field(i).Interface()})
				}
			} else {
				condition.SetWhere(fmt.Sprintf("\"%s\".\"%s\" > ? and \"%s\".\"%s\" is not null", t.Table, t.Column, t.Table, t.Column), []interface{}{qValue.Field(i).Interface()})
			}
		case "gte":
			if len(t.JsonField) > 0 {
				dt := qType.Field(i).Type.Name()
				switch dt {
				case "string":
					condition.SetWhere(fmt.Sprintf("(\"%s\".\"%s\" #>>'{%s}')::VARCHAR >= ?", t.Table, t.Column, t.JsonField), []interface{}{qValue.Field(i).String()})
				default:
					condition.SetWhere(fmt.Sprintf("(\"%s\".\"%s\" #>>'{%s}')::INTEGER >= ?", t.Table, t.Column, t.JsonField), []interface{}{qValue.Field(i).Interface()})
				}
			} else {
				condition.SetWhere(fmt.Sprintf("\"%s\".\"%s\" >= ? and \"%s\".\"%s\" is not null", t.Table, t.Column, t.Table, t.Column), []interface{}{qValue.Field(i).Interface()})
			}
		case "lt":
			if len(t.JsonField) > 0 {
				dt := qType.Field(i).Type.Name()
				switch dt {
				case "string":
					condition.SetWhere(fmt.Sprintf("(\"%s\".\"%s\" #>>'{%s}')::VARCHAR < ?", t.Table, t.Column, t.JsonField), []interface{}{qValue.Field(i).String()})
				default:
					condition.SetWhere(fmt.Sprintf("(\"%s\".\"%s\" #>>'{%s}')::INTEGER < ?", t.Table, t.Column, t.JsonField), []interface{}{qValue.Field(i).Interface()})
				}
			} else {
				condition.SetWhere(fmt.Sprintf("\"%s\".\"%s\" < ? and \"%s\".\"%s\" is not null", t.Table, t.Column, t.Table, t.Column), []interface{}{qValue.Field(i).Interface()})
			}
		case "lte":
			if len(t.JsonField) > 0 {
				dt := qType.Field(i).Type.Name()
				switch dt {
				case "string":
					condition.SetWhere(fmt.Sprintf("(\"%s\".\"%s\" #>>'{%s}')::VARCHAR <= ?", t.Table, t.Column, t.JsonField), []interface{}{qValue.Field(i).String()})
				default:
					condition.SetWhere(fmt.Sprintf("(\"%s\".\"%s\" #>>'{%s}')::INTEGER <= ?", t.Table, t.Column, t.JsonField), []interface{}{qValue.Field(i).Interface()})
				}
			} else {
				condition.SetWhere(fmt.Sprintf("\"%s\".\"%s\" <= ? and \"%s\".\"%s\" is not null", t.Table, t.Column, t.Table, t.Column), []interface{}{qValue.Field(i).Interface()})
			}
		case "startswith", "istartswith":
			if driver == search.Postgres && t.Type == "istartswith" {
				condition.SetWhere(fmt.Sprintf("\"%s\".\"%s\" ilike ?", t.Table, t.Column), []interface{}{qValue.Field(i).String() + "%"})
			} else {
				condition.SetWhere(fmt.Sprintf("\"%s\".\"%s\" like ?", t.Table, t.Column), []interface{}{qValue.Field(i).String() + "%"})
			}
		case "endswith", "iendswith":
			if driver == search.Postgres && t.Type == "iendswith" {
				condition.SetWhere(fmt.Sprintf("\"%s\".\"%s\" ilike ?", t.Table, t.Column), []interface{}{"%" + qValue.Field(i).String()})
			} else {
				condition.SetWhere(fmt.Sprintf("\"%s\".\"%s\" like ?", t.Table, t.Column), []interface{}{"%" + qValue.Field(i).String()})
			}
		case "in":
			condition.SetWhere(fmt.Sprintf("\"%s\".\"%s\" in (?)", t.Table, t.Column), []interface{}{qValue.Field(i).Interface()})
		case "inWithNull":
			condition.SetWhere(fmt.Sprintf("\"%s\".\"%s\" in (?) or \"%s\".\"%s\" is null", t.Table, t.Column, t.Table, t.Column), []interface{}{qValue.Field(i).Interface()})
		case "inWithNullStr":
			condition.SetWhere(fmt.Sprintf("\"%s\".\"%s\" in (?) or \"%s\".\"%s\" is null or \"%s\".\"%s\" = ''", t.Table, t.Column, t.Table, t.Column, t.Table, t.Column), []interface{}{qValue.Field(i).Interface()})
		case "nin":
			condition.SetWhere(fmt.Sprintf("\"%s\".\"%s\" not in (?)", t.Table, t.Column), []interface{}{qValue.Field(i).Interface()})
		case "ninOrNil":
			condition.SetWhere(fmt.Sprintf("\"%s\".\"%s\" not in (?) or \"%s\".\"%s\" is null", t.Table, t.Column, t.Table, t.Column), []interface{}{qValue.Field(i).Interface()})
		case "isnull":
			if !(qValue.Field(i).IsZero() && qValue.Field(i).IsNil()) {
				condition.SetWhere(fmt.Sprintf("\"%s\".\"%s\" is null", t.Table, t.Column), make([]interface{}, 0))
			}
		case "isNil":
			if !(qValue.Field(i).IsZero() && qValue.Field(i).IsNil()) {
				condition.SetWhere(fmt.Sprintf("\"%s\".\"%s\" is null or \"%s\".\"%s\" = ''", t.Table, t.Column, t.Table, t.Column), make([]interface{}, 0))
			}
		case "notnull":
			if !(qValue.Field(i).IsZero() && qValue.Field(i).IsNil()) {
				if len(t.JsonField) > 0 {
					condition.SetWhere(fmt.Sprintf("(\"%s\".\"%s\" #>>'{%s}') is not null", t.Table, t.Column, t.JsonField), make([]interface{}, 0))
				} else {
					condition.SetWhere(fmt.Sprintf("\"%s\".\"%s\" is not null", t.Table, t.Column), make([]interface{}, 0))
				}
			}
		case "order":
			switch strings.ToLower(qValue.Field(i).String()) {
			case "desc", "asc":
				condition.SetOrder(fmt.Sprintf("\"%s\".\"%s\" %s", t.Table, t.Column, qValue.Field(i).String()))
			}
		case "eqOr":
			orColumns := strings.Split(strings.ReplaceAll(t.Column, " ", ""), ",")
			fstr := ""
			placeVals := make([]interface{}, 0)
			theVal := qValue.Field(i).Interface()
			for i, col := range orColumns {
				fstr += fmt.Sprintf("\"%s\".\"%s\" = ? ", t.Table, col)
				placeVals = append(placeVals, theVal)
				if i != len(orColumns)-1 {
					fstr += " or "
				}
			}
			condition.SetWhere(fstr, placeVals)
		case "inOr":
			orColumns := strings.Split(strings.ReplaceAll(t.Column, " ", ""), ",")
			fstr := ""
			placeVals := make([]interface{}, 0)
			theVal := qValue.Field(i).Interface()
			for i, col := range orColumns {
				fstr += fmt.Sprintf("\"%s\".\"%s\" in (?) ", t.Table, col)
				placeVals = append(placeVals, theVal)
				if i != len(orColumns)-1 {
					fstr += " or "
				}
			}
			condition.SetWhere(fstr, placeVals)
		case "likeOr":
			orColumns := strings.Split(strings.ReplaceAll(t.Column, " ", ""), ",")
			fstr := ""
			placeVals := make([]interface{}, 0)
			theVal := qValue.Field(i).String()
			for i, col := range orColumns {
				fstr += fmt.Sprintf("\"%s\".\"%s\" like ? ", t.Table, col)
				placeVals = append(placeVals, []interface{}{"%" + theVal + "%"})
				if i != len(orColumns)-1 {
					fstr += " or "
				}
			}
			condition.SetWhere(fstr, placeVals)
		case "ne":
			condition.SetWhere(fmt.Sprintf("\"%s\".\"%s\" != ?", t.Table, t.Column), []interface{}{qValue.Field(i).Interface()})
		case "neOrNil":
			condition.SetWhere(fmt.Sprintf("\"%s\".\"%s\" != ? or \"%s\".\"%s\" is null", t.Table, t.Column, t.Table, t.Column), []interface{}{qValue.Field(i).Interface()})
		case "concatin":
			theColumns := strings.Split(strings.ReplaceAll(t.Column, " ", ""), ",")
			fstr := ""
			placeVals := make([]interface{}, 0)
			theVal := qValue.Field(i).Interface()
			for i, col := range theColumns {
				fstr += fmt.Sprintf("\"%s\".\"%s\"", t.Table, col)
				if i < len(theColumns)-1 {
					fstr += " || '|' || "
				}
			}
			fstr += " in ?"
			placeVals = append(placeVals, theVal)
			condition.SetWhere(fstr, placeVals)
		case "concatnin":
			theColumns := strings.Split(strings.ReplaceAll(t.Column, " ", ""), ",")
			fstr := ""
			placeVals := make([]interface{}, 0)
			theVal := qValue.Field(i).Interface()
			for i, col := range theColumns {
				fstr += fmt.Sprintf("\"%s\".\"%s\"", t.Table, col)
				if i < len(theColumns)-1 {
					fstr += " || '|' || "
				}
			}
			fstr += " not in ?"
			placeVals = append(placeVals, theVal)
			condition.SetWhere(fstr, placeVals)
		}
	}
}

type resolveSearchTag struct {
	Type      string
	Column    string
	Table     string
	On        []string
	Join      string
	JsonField string
}

// makeTag 解析search的tag标签
func makeTag(tag string) *resolveSearchTag {
	r := &resolveSearchTag{}
	tags := strings.Split(tag, ";")
	var ts []string
	for _, t := range tags {
		ts = strings.Split(t, ":")
		if len(ts) == 0 {
			continue
		}
		switch ts[0] {
		case "type":
			if len(ts) > 1 {
				r.Type = ts[1]
			}
		case "column":
			if len(ts) > 1 {
				r.Column = ts[1]
			}
		case "table":
			if len(ts) > 1 {
				r.Table = ts[1]
			}
		case "on":
			if len(ts) > 1 {
				r.On = ts[1:]
			}
		case "join":
			if len(ts) > 1 {
				r.Join = ts[1]
			}
		case "jsonField":
			if len(ts) > 1 {
				r.JsonField = ts[1]
			}
		}
	}
	return r
}
